package com.demo.common.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.IdcardUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;

import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.*;

/**
 * @author lxf
 * @description
 * @date 2021/3/4 22:39
 */
public class ExportToExcelUtils {

    /**
     * 每个sheet 最大限制行数，超过这个值输出到sheet2，sheet3...
     */
    public static final int MAX_ROW = 10;
    private static Log log = LogFactory.get(ExportToExcelUtils.class);
//-----------------------------------------------------------------------------export to excel start

    /**
     * 导出数据到excel
     *
     * @param outputStream 输出流
     * @param clazz        映射实体类型
     * @param sheetName    sheet名称 例如 sheet1 sheet2
     * @param excelType    导出文件类型 例如 .xls .xlsx
     * @param writeList    写出数据，集合形式 List<clazz>
     */
    public static void export(OutputStream outputStream, Class<? extends BaseRowModel> clazz, String sheetName, ExcelTypeEnum excelType, List writeList) {
        long exportStartTime = System.currentTimeMillis();
        log.info("导出总数Size: {}条", writeList.size());
        EasyExcel.write(outputStream).needHead(true)
                .autoCloseStream(true)
                .head(clazz)
                .excelType(excelType)
                .registerWriteHandler(new Custemhandler())
                .registerWriteHandler(Custemhandler.getStyleStrategy())
                .sheet(1, sheetName)
                .doWrite(writeList);
        log.info("导出结束时间:{};导出耗时: {}毫秒", DateUtil.now(), (System.currentTimeMillis() - exportStartTime));
    }


    /**
     * @param outputStream
     * @param writeList    查询结果
     * @param excelType    导出文件类型 .xls .xlsx
     * @param clazz        映射实体class类 继承{@link BaseRowModel}
     * @param <T>          查询结果类型
     * @throws Exception
     * @author lxf
     * @createDate 2021-03-05
     */
    public static <T> void exportMore(OutputStream outputStream, Class<? extends BaseRowModel> clazz, ExcelTypeEnum excelType, List<T> writeList) throws Exception {
        long exportStartTime = System.currentTimeMillis();
        log.info("导出总数Size: {}条", writeList.size());
        List<List<T>> lists = splitList(writeList, MAX_ROW); // 分割的集合
        ExcelWriter excelWriter = EasyExcel.write(outputStream)
                .needHead(true)
                .head(clazz)
                .autoCloseStream(true)
                .excelType(excelType)
                .registerWriteHandler(new Custemhandler())
                .registerWriteHandler(Custemhandler.getStyleStrategy())
                .build();
        ExcelWriterSheetBuilder excelWriterSheetBuilder;
        WriteSheet writeSheet;
        for (int i = 1; i <= lists.size(); i++) {
            excelWriterSheetBuilder = new ExcelWriterSheetBuilder(excelWriter);
            excelWriterSheetBuilder.sheetNo(i);
            excelWriterSheetBuilder.sheetName("sheet" + i);
            writeSheet = excelWriterSheetBuilder.build();
            excelWriter.write(lists.get(i - 1), writeSheet);
        }
        outputStream.flush();
        excelWriter.finish();
        log.info("导出结束时间:{};导出耗时: {}毫秒", DateUtil.now(), (System.currentTimeMillis() - exportStartTime));
    }

//-----------------------------------------------------------------------------export to excel end


//-----------------------------------------------------------------------------read to get output stream start


    /**
     * 获取输出流，输出到浏览器
     *
     * @param fileName 文件名称
     * @param response 响应
     * @param fileType excel文件后缀类型 .xls 或 .xlsx
     * @return
     * @throws Exception
     */
    public static OutputStream getOutputStream(String fileName, HttpServletResponse response, ExcelTypeEnum fileType) throws Exception {
        fileName = StrUtil.isEmpty(fileName) ? IdUtil.fastSimpleUUID() : fileName;
        fileName = URLEncoder.encode(fileName, "UTF-8");
        if (ExcelTypeEnum.XLSX.equals(fileType)) {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        } else {
            response.setContentType("application/vnd.ms-excel");
        }
        response.setCharacterEncoding("utf8");
        response.setHeader("Content-Disposition", "attachment; filename=" + fileName + fileType.getValue());
        response.setHeader("Pragma", "public");
        response.setHeader("Cache-Control", "no-store");
        response.addHeader("Cache-Control", "max-age=0");
        return response.getOutputStream();
    }


//-----------------------------------------------------------------------------read to get output stream end

//-----------------------------------------------------------------------------read to split list start

    /**
     * 将list集合按照 len大小切割为多个集合
     * update lxf 2021-03-05
     *
     * @param list 待切割集合
     * @param len  集合按照多大size来切割
     * @param <T>
     * @return
     */
    private static <T> List<List<T>> splitList(List<T> list, int len) {
        if (list == null || list.size() == 0 || len < 1) {
            return null;
        }
        List<List<T>> result = new ArrayList<>();
        int size = list.size();
        int count = (size + len - 1) / len;

        for (int i = 0; i < count; i++) {
            List<T> subList = list.subList(i * len, (Math.min((i + 1) * len, size)));
            result.add(subList);
        }
        return result;
    }


    /**
     * 集合平均分组
     *
     * @param source 源集合
     * @param n      分成n个集合
     * @param <T>    集合类型
     * @return
     */
    private static <T> List<List<T>> groupList(List<T> source, int n) {
        if (source == null || source.size() == 0 || n < 1) {
            return null;
        }
        if (source.size() < n) {
            return Arrays.asList(source);
        }
        List<List<T>> result = new ArrayList<List<T>>();
        int number = source.size() / n;
        int remaider = source.size() % n;
        int offset = 0; // 偏移量，每有一个余数分配，就要往右偏移一位
        for (int i = 0; i < n; i++) {
            List<T> list1 = null;
            if (remaider > 0) {
                list1 = source.subList(i * number + offset, (i + 1) * number + offset + 1);
                remaider--;
                offset++;
            } else {
                list1 = source.subList(i * number + offset, (i + 1) * number + offset);
            }
            result.add(list1);
        }

        return result;
    }

}
//-----------------------------------------------------------------------------read to split list end


/**
 * 列宽策略处理器
 */
class Custemhandler extends AbstractColumnWidthStyleStrategy {

    private static final int MAX_COLUMN_WIDTH = 255;
    //因为在自动列宽的过程中，有些设置地方让列宽显得紧凑，所以做出了个判断
    private static final int COLUMN_WIDTH = 20;
    private Map<Integer, Map<Integer, Integer>> CACHE = new HashMap(8);

    @Override
    protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head, Integer integer, Boolean isHead) {
        boolean needSetWidth = isHead || !CollectionUtil.isEmpty(cellDataList);
        if (needSetWidth) {
            Map<Integer, Integer> maxColumnWidthMap = (Map) CACHE.get(writeSheetHolder.getSheetNo());
            if (maxColumnWidthMap == null) {
                maxColumnWidthMap = new HashMap(16);
                CACHE.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);
            }

            Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
            if (columnWidth >= 0) {
                if (columnWidth > MAX_COLUMN_WIDTH) {
                    columnWidth = MAX_COLUMN_WIDTH;
                } else {
                    if (columnWidth < COLUMN_WIDTH) {
                        columnWidth = columnWidth * 2;
                    }
                }

                Integer maxColumnWidth = (Integer) ((Map) maxColumnWidthMap).get(cell.getColumnIndex());
                if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                    ((Map) maxColumnWidthMap).put(cell.getColumnIndex(), columnWidth);
                    writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
                }
            }
        }
    }

    /**
     * 判断列宽
     * @param cellDataList
     * @param cell
     * @param isHead
     * @return
     */
    private Integer dataLength(List<CellData> cellDataList, Cell cell, Boolean isHead) {
        if (isHead) {
            return cell.getStringCellValue().getBytes().length;
        } else {
            CellData cellData = (CellData) cellDataList.get(0);
            CellDataTypeEnum type = cellData.getType();
            if (type == null) {
                return -1;
            } else {
                switch (type) {
                    case STRING:
                        return cellData.getStringValue().getBytes().length;
                    case BOOLEAN:
                        return cellData.getBooleanValue().toString().getBytes().length;
                    case NUMBER:
                        return cellData.getNumberValue().toString().getBytes().length;
                    default:
                        return -1;
                }
            }
        }
    }

    /**
     * 获取表格样式策略
     * @return
     */
    public static HorizontalCellStyleStrategy getStyleStrategy() {
        // 头的策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 背景设置为灰色
        headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short) 12);
        // 字体样式
        headWriteFont.setFontName("Frozen");
        headWriteCellStyle.setWriteFont(headWriteFont);
        //自动换行
        headWriteCellStyle.setWrapped(false);
        // 水平对齐方式
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 垂直对齐方式
        headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        // 内容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
//        contentWriteCellStyle.setFillPatternType(FillPatternType.SQUARES);
        // 背景白色
        contentWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
        WriteFont contentWriteFont = new WriteFont();
        // 字体大小
        contentWriteFont.setFontHeightInPoints((short) 12);
        // 字体样式
        contentWriteFont.setFontName("Calibri");
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        // 水平对齐方式
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 垂直对齐方式
        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    }

}